/*
 * TailFile module for Netbeans.
 *
 * Copyright (c) 2007, Rogue
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * For queries or bugs, you may contact me via e-mail: roguexz [at] gmail [dot] com
 */
package rogue.netbeans.module.tail;

import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;

import org.openide.windows.IOProvider;
import org.openide.windows.InputOutput;
import org.openide.windows.OutputWriter;
import org.openide.windows.WindowManager;

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

import java.util.ArrayList;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;


/**
 * Monitors a given file and prints out the data to a tab within the Output
 * window.
 *
 * @author Rogue
 */
public class FileMonitor implements Runnable {
    private static final String CLOSE_IMAGE_LOCATION = "/rogue/netbeans/module/tail/images/close.png";
    private static final String BASE_NAME = "Tail - ";
    private InputOutput provider = null;
    private File fileToMonitor = null;
    private AbstractAction closeButtonAction = null;
    private boolean terminate = false;
    private List threadListeners = new ArrayList();
    private WindowAdapter windowClosingListener = new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
            terminate();
        }
    };

    /**
     * Creates a new instance of FileMonitor
     *
     * @param fileToMonitor a handle to the <code>File</code> that needs to be
     *        monitored.
     */
    public FileMonitor(File fileToMonitor) {
        this.fileToMonitor = fileToMonitor;
        closeButtonAction = new AbstractAction() {

                {
                    putValue(Action.SHORT_DESCRIPTION, "Terminate");
                    putValue(Action.SMALL_ICON,
                             new ImageIcon(getClass().getResource(CLOSE_IMAGE_LOCATION)));
                }

                public void actionPerformed(ActionEvent e) {
                    terminate();
                }
            };

        WindowManager.getDefault().getMainWindow().addWindowListener(windowClosingListener);
    }

    public void run() {
        provider = IOProvider.getDefault().getIO(
                new StringBuilder(BASE_NAME).append(fileToMonitor.getName()).toString(),
                new Action[] { closeButtonAction });
        
        // Ensure that the tab is visible to the user.
        provider.select();

        OutputWriter writer = provider.getOut();
        writer.println(
                new StringBuilder("Tailing file: ").
                append(fileToMonitor.getAbsolutePath()).toString());

        RandomAccessFile raf = null;

        try {
            raf = new RandomAccessFile(fileToMonitor, "r");

            // set the seek position to the end of the file.
            raf.seek(fileToMonitor.length());

            while (!terminate) {
                Thread.sleep(1000);

                long currentSeekPointer = raf.getFilePointer();
                long currentFileSize = fileToMonitor.length();
                long diff = currentFileSize - currentSeekPointer;

                // Check if the file got recreated, i.e if diff <0
                if(diff<0) {
                    currentSeekPointer = 0;
                }
                
                // Check for JDK bug. On linux, if a file has been modified, then
                // the currentFileSize returns the correct value, but
                // RandomAccessFile.length() gives us the old value. It is possible
                // that the underlying file descriptor is not getting updated.
                if (raf.length() != currentFileSize) {
                    // reset the handles.
                    raf = new RandomAccessFile(fileToMonitor, "r");
                    raf.seek(currentSeekPointer);
                }

                
                while ((diff > 0) && !terminate) {
                    // Read and print data.
                    int x = ((diff - 1024) > 0) ? 1024 : (int) diff;

                    byte[] data = new byte[x];
                    diff -= raf.read(data);
                    writer.print(new String(data));
                }
            }
        } catch (FileNotFoundException ex) {
            DialogDisplayer.getDefault().notify(new NotifyDescriptor.Exception(ex));
        } catch (InterruptedException ex) {
            DialogDisplayer.getDefault().notify(new NotifyDescriptor.Exception(ex));
        } catch (IOException ex) {
            DialogDisplayer.getDefault().notify(new NotifyDescriptor.Exception(ex));
        } finally {
            if (raf != null) {
                try {
                    raf.close();
                } catch (IOException ex) {
                    DialogDisplayer.getDefault().notify(new NotifyDescriptor.Exception(ex));
                }
            }

            writer.println("\nTail terminated.");
            EventQueue.invokeLater(new Runnable() {
                    public void run() {
                        WindowManager.getDefault().getMainWindow()
                                     .removeWindowListener(windowClosingListener);
                    }
                });
            notifyListeners();
        }
    }

    public void ensureVisible() {
        if (provider != null) {
            provider.select();
        }
    }

    public File getFile() {
        return fileToMonitor;
    }

    private void terminate() {
        this.terminate = true;
    }

    public void addFileMonitorListener(FileMonitorListener listener) {
        threadListeners.add(listener);
    }

    public void removeFileMonitorListener(FileMonitorListener listener) {
        threadListeners.remove(listener);
    }

    private void notifyListeners() {
        int size = threadListeners.size();

        for (int i = 0; i < size; i++) {
            ((FileMonitorListener) threadListeners.get(i)).taskTerminated(this);
        }
    }
}
